// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable CommentTypo

/* FormatParser.cs --
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Runtime.CompilerServices;

#endregion

namespace AM.Buffers.Text;

internal static class FormatParser
{
    // {index[,alignment][:formatString]}

    public readonly ref struct ParseResult
    {
        public readonly int Index;
        public readonly ReadOnlySpan<char> FormatString;
        public readonly int LastIndex;
        public readonly int Alignment;

        public ParseResult (int index, ReadOnlySpan<char> formatString, int lastIndex, int alignment)
        {
            Index = index;
            FormatString = formatString;
            LastIndex = lastIndex;
            Alignment = alignment;
        }
    }

    internal const int ArgLengthLimit = 16;
    internal const int WidthLimit = 1000; // Note:  -WidthLimit <  ArgAlign < WidthLimit

    [MethodImpl (MethodImplOptions.AggressiveInlining)]
    public static ParserScanResult ScanFormatString (string format, ref int i)
    {
        var len = format.Length;
        char c = format[i];

        i++; // points netxt char
        if (c == '}')
        {
            // skip escaped '}'
            if (i < len && format[i] == '}')
            {
                i++;
                return ParserScanResult.EscapedChar;
            }
            else
            {
                ExceptionUtil.ThrowFormatError();
                return ParserScanResult.NormalChar; // NOTE Don't reached
            }
        }
        else if (c == '{')
        {
            // skip escaped '{'
            if (i < len && format[i] == '{')
            {
                i++;
                return ParserScanResult.EscapedChar;
            }
            else
            {
                i--;
                return ParserScanResult.BraceOpen;
            }
        }
        else
        {
            // ch is the normal char OR end of text
            return ParserScanResult.NormalChar;
        }
    }

    [MethodImpl (MethodImplOptions.AggressiveInlining)]
    public static ParserScanResult ScanFormatString (ReadOnlySpan<char> format, ref int i)
    {
        var len = format.Length;
        char c = format[i];

        i++; // points netxt char
        if (c == '}')
        {
            // skip escaped '}'
            if (i < len && format[i] == '}')
            {
                i++;
                return ParserScanResult.EscapedChar;
            }
            else
            {
                ExceptionUtil.ThrowFormatError();
                return ParserScanResult.NormalChar; // NOTE Don't reached
            }
        }
        else if (c == '{')
        {
            // skip escaped '{'
            if (i < len && format[i] == '{')
            {
                i++;
                return ParserScanResult.EscapedChar;
            }
            else
            {
                i--;
                return ParserScanResult.BraceOpen;
            }
        }
        else
        {
            // ch is the normal char OR end of text
            return ParserScanResult.NormalChar;
        }
    }

    // Accept only non-unicode numbers
    [MethodImpl (MethodImplOptions.AggressiveInlining)]
    private static bool IsDigit (char c) => '0' <= c && c <= '9';

    [MethodImpl (MethodImplOptions.AggressiveInlining)]
    public static ParseResult Parse (ReadOnlySpan<char> format, int i)
    {
        char c = default;
        var len = format.Length;

        i++; // Skip `{`

        //  === Index Component ===
        //   ('0'-'9')+ WS*

        if (i == len || !IsDigit (c = format[i]))
        {
            ExceptionUtil.ThrowFormatError();
        }

        int paramIndex = 0;
        do
        {
            paramIndex = (paramIndex * 10) + c - '0';

            if (++i == len)
                ExceptionUtil.ThrowFormatError();

            c = format[i];
        } while (IsDigit (c) && paramIndex < ArgLengthLimit);

        if (paramIndex >= ArgLengthLimit)
        {
            ExceptionUtil.ThrowFormatException();
        }

        // skip whitespace.
        while (i < len && (c = format[i]) == ' ')
            i++;

        //  === Alignment Component ===
        //   comma WS* minus? ('0'-'9')+ WS*

        int alignment = 0;

        if (c == ',')
        {
            i++;

            // skip whitespace.
            while (i < len && (c = format[i]) == ' ')
                i++;

            if (i == len)
            {
                ExceptionUtil.ThrowFormatError();
            }

            var leftJustify = false;
            if (c == '-')
            {
                leftJustify = true;

                if (++i == len)
                    ExceptionUtil.ThrowFormatError();

                c = format[i];
            }

            if (!IsDigit (c))
            {
                ExceptionUtil.ThrowFormatError();
            }

            do
            {
                alignment = (alignment * 10) + c - '0';

                if (++i == len)
                    ExceptionUtil.ThrowFormatError();

                c = format[i];
            } while (IsDigit (c) && alignment < WidthLimit);

            if (leftJustify)
                alignment *= -1;
        }

        // skip whitespace.
        while (i < len && (c = format[i]) == ' ')
            i++;

        //  === Format String Component ===

        ReadOnlySpan<char> itemFormatSpan = default;

        if (c == ':')
        {
            i++;
            int formatStart = i;

            while (true)
            {
                if (i == len)
                {
                    ExceptionUtil.ThrowFormatError();
                }

                c = format[i];

                if (c == '}')
                {
                    break;
                }
                else if (c == '{')
                {
                    ExceptionUtil.ThrowFormatError();
                }

                i++;
            }

            // has format
            if (i > formatStart)
            {
                itemFormatSpan = format.Slice (formatStart, i - formatStart);
            }
        }
        else if (c != '}')
        {
            // Unexpected character
            ExceptionUtil.ThrowFormatError();
        }

        i++; // Skip `}`
        return new ParseResult (paramIndex, itemFormatSpan, i, alignment);
    }

    [MethodImpl (MethodImplOptions.AggressiveInlining)]
    public static ParseResult Parse (string format, int i)
    {
        char c = default;
        var len = format.Length;

        i++; // Skip `{`

        //  === Index Component ===
        //   ('0'-'9')+ WS*

        if (i == len || !IsDigit (c = format[i]))
        {
            ExceptionUtil.ThrowFormatError();
        }

        int paramIndex = 0;
        do
        {
            paramIndex = (paramIndex * 10) + c - '0';

            if (++i == len)
                ExceptionUtil.ThrowFormatError();

            c = format[i];
        } while (IsDigit (c) && paramIndex < ArgLengthLimit);

        if (paramIndex >= ArgLengthLimit)
        {
            ExceptionUtil.ThrowFormatException();
        }

        // skip whitespace.
        while (i < len && (c = format[i]) == ' ')
            i++;

        //  === Alignment Component ===
        //   comma WS* minus? ('0'-'9')+ WS*

        int alignment = 0;

        if (c == ',')
        {
            i++;

            // skip whitespace.
            while (i < len && (c = format[i]) == ' ')
                i++;

            if (i == len)
            {
                ExceptionUtil.ThrowFormatError();
            }

            var leftJustify = false;
            if (c == '-')
            {
                leftJustify = true;

                if (++i == len)
                    ExceptionUtil.ThrowFormatError();

                c = format[i];
            }

            if (!IsDigit (c))
            {
                ExceptionUtil.ThrowFormatError();
            }

            do
            {
                alignment = (alignment * 10) + c - '0';

                if (++i == len)
                    ExceptionUtil.ThrowFormatError();

                c = format[i];
            } while (IsDigit (c) && alignment < WidthLimit);

            if (leftJustify)
                alignment *= -1;
        }

        // skip whitespace.
        while (i < len && (c = format[i]) == ' ')
            i++;

        //  === Format String Component ===

        ReadOnlySpan<char> itemFormatSpan = default;

        if (c == ':')
        {
            i++;
            int formatStart = i;

            while (true)
            {
                if (i == len)
                {
                    ExceptionUtil.ThrowFormatError();
                }

                c = format[i];

                if (c == '}')
                {
                    break;
                }
                else if (c == '{')
                {
                    ExceptionUtil.ThrowFormatError();
                }

                i++;
            }

            // has format
            if (i > formatStart)
            {
                itemFormatSpan = format.AsSpan (formatStart, i - formatStart);
            }
        }
        else if (c != '}')
        {
            // Unexpected character
            ExceptionUtil.ThrowFormatError();
        }

        i++; // Skip `}`
        return new ParseResult (paramIndex, itemFormatSpan, i, alignment);
    }
}

internal enum ParserScanResult
{
    BraceOpen,
    EscapedChar,
    NormalChar,
}
